from django.db import models

# Create your models here.


'''
找的是 app1_blog 这个表；
注意：表的字段不能和框架api有冲突，文档中指定不能是 clean、save、delete。
  当然也不能是python 的关键字和保留字。
  不能包含连续两个 _ ，因为这是Django 内置的一些功能的实现条件。
  名称最后不能以 _ 结尾。
  可以使用SQL关键字，join、where、select等等。因为Django会在查询之前会进行转义。
  
jdango.db.models.Options.db_table;
'''
class Blog(models.Model):
    # 按文档的说法，CharField 这种都是类，每个属性都是类的一个实例。
    # 目前没有找到设置别名的方式。意味着类的属性名和表的字段名要全部一致。
    # 后来看api，可能是每个 Field 类实例的参数 db_column 确定表的字段名。
    
    # 有些字段类需要必须的属性，比如CharField 需要 max_length，不仅仅用于表的字段设置，还会用于验证。

    title = models.CharField(max_length=255)
    # 失败了，本想着表里使用 summary，字段名使用 summary_haha。
    # 程序报错：表的字段 summary_haha 不存在~~
    #summary_haha = models.CharField('summary', max_length=255)
    summary = models.CharField('summary', max_length=255)
    # 参数 created_at 改成 created_at2 也不影响获取成功，这是什么意思？
    # 我初步估计：这个字符串参数应该是创建表时该字段的注释；
    created_at = models.DateTimeField('datetime created_at2')
    
    # 一些源信息，比如指定表名；
    # 内部类（可选的，每个实现也都是可选的）
    class Meta:
        ordering = ["排序字段，应该是每次查询都会进行排序"]
        db_table = "指定表名"
        verbose_name = ""
        verbose_name_plural = ""
    
    '''
    models.IntegerField();
    models.CharField(必须有一个参数 max_lenght);
    models.DateTimeField();
    models.DateField();
    models.TextField();
    models.EmailField();
    
    models.ManyToManyField(Author);
    models.ForeignKey(Blog, on_delete=models.CASCADE)
    '''
    
    # 类属性
    # objects
    # 一个模型类的最重要的属性就是 Manager（这是一个接口，也就是某个属性应该是其实现类的类型）。
    # 如果没有自定义这样的类型属性，那么有一个默认的属性，就是 objects。
    # 这样的属性，只能使用类来访问，而不能通过类的实例。
    
    '''
    【where】
    语法：field__lookuptype=value；
    fieldname__startswith='...'；
    fieldname__istartswith='...'；不区分大小写。
    fieldname__endswith='...'；
    fieldname__iendswith='...'；不区分大小写。
    fieldname__gt='...'；
    fieldname__gte='...' 对应 >=；
    fieldname__lt='...'；
    fieldname__lte='...' 对应 <=；
    fieldname__exact='...' 对应 =；如果给的参数中，key不含 __，那么就是 =。
    fieldname__iexact='...' 忽略大小写的进行 = 比较查询。
    fieldname__contains='...' 对应 like '%值%'。当然区分大小写。
    fieldname__icontains='...' 对应 like '%值%'。当然不区分大小写。
    fieldname__year='...'；
    
    blog__name='Beatles Blog' 联查，join，Blog模型类的 name 属性值是指定值。
    entry__headline__contains='Lennon' 联查，Entry模型类的 headline 属性值 like '%Lennon%'
    
    Blog.objects.filter(entry__authors__name='Lennon')
    多层次的联查
    如果中间某个模型类不具有匹配条件，Django 不会报错，而是认为有一个空的、有效的对象，只是这个对象的所有的值都是 NULL。
    也就是说，上例中，Entry 模型类关联 Authors 模型类，而 Authors 模型类的 name 的值没有匹配 Lennon 的，则不会报错。
    
    同一个表的两个字段进行比较
    使用 Django 提供的 F() 功能，F() 引用当前的模型类本身。
    n_comments__gt=F('n_pingbacks') 相当于 `n_comments` > `n_pingbacks`；
    还支持表达式
    n_comments__gt=F('n_pingbacks') * 2；
    rating__lt=F('n_comments') + F('n_pingbacks')；
    authors__name=F('blog__name')；
    对于 date and date/time 字段，可以对 timedelta() 进行加减：
    mod_date__gt=F('pub_date') + timedelta(days=3)；发布后3天进行了更改了的。
    按位运算：
    .bitand(), .bitor(), .bitrightshift(), and .bitleftshift()，
    F('somefield').bitand(16)。
    
    
    主键的
    pk=...；
    pk__in=[1,2,3]；
    pk__gt=1；
    ...
    
    blog__id__exact=3 === blog__id=3 === blog__pk=3；
    
    对于执行 LIKE 的 SQL（iexact, contains, icontains, startswith, istartswith, endswith and iendswith），会对 % 和 _ 进行转义：
    headline__contains='%' => headline LIKE '%\%%';
    
    
    
    【查询】
    ModelClassName.objects 是一个 Manager 实例；
    ModelClassName.objects.all();返回一个 QuerySet，包含了该表的所有数据；
    QuerySet 的过滤器：可以有0个、1个、很多个。过滤器一般是 where、limit等。
    filter(**kwargs) 返回符合指定条件的；
    res = ModelClassName.objects.filter(关键字传参：field1=value1,field2=value2,...);自动执行了select操作，直接返回查询到的数据。
    exclude(**kwargs) 排除符合指定条件的；
    
    ModelClassName.objects.filter() 相当于 ModelClassName.objects.all().filter();
    
    过滤器链：
    ModelClassName.objects.filter(...).exclude(...).filter(...);
    
    每个过滤器都返回一个新的 QuerySet：
    q = Entry.objects.filter(headline__startswith="What")
    r1 = q.exclude(pub_date__gte=datetime.date.today())
    r2 = q.filter(pub_date__gte=datetime.date.today())
    q 中的数据，都是 字段 headline 以 What 开头的；
    r1 则在 q 的基础上，获取今天以前的那些数据；
    r2 则在 q 的基础上，获取今天及以后的那些数据；
    
    这些过滤器，本身都是lazy 的，不会立即执行，而是在使用的时候才会执行。
    也就是说只有使用 r1 或者 r2 时才会真正的执行 SQL。
    而且，不管使用 r1 还是 r2 ，还是都使用，本质上只执行一次 SQL。
    
    使用过滤器的方式，即使结果只有一条数据，得到的结果仍然是一个集合 QuerySet。
    
    如果只想得到一个对象，则使用 get：
    res = ModelClassName.objects.get(关键字传参：field1=value1,field2=value2,...);
    get() 的参数和过滤器的参数类似。主键则使用 pk，比如 pk=1。
    如果get() 的没有结果，会抛出一个异常 DoesNotExist。这个异常是属于当前模型类的：Class.DoesNotExist。
    换句话说，每个模型类都有这么一个属性。
    有个很bug的事情：如果get() 的条件匹配到了数据多于 1 条，也会抛出异常：MultipleObjectsReturned，也是属于当前模型类的一个属性。
    这意味着，只能在确切的知道匹配到的数据只有一条时，才能使用 get()。
    
    排序过滤器
    ModelClassName.objects.order_by('field');
    
    limit
    ModelClassName.objects.all()[:5]; === limit 5；
    ModelClassName.objects.all()[-1]; 不支持负索引。
    ModelClassName.objects.all()[5:10]; === limit 5,10；
    会返回新的 QuerySet；不会真的执行查询。也就是说，还是延迟执行。
    不过，如果使用了 python 的步进值，那么会确实立即执行查询操作：    
    ModelClassName.objects.all()[:10:2]; 就会立即执行。
    对于使用了切片的QuerySet，不能再使用过滤器和排序功能了，因为会比较混乱，不知道要如何工作。
    如果只想获取一个数据，limit 1，不要使用切片式参数，而仅仅使用简单的索引式：
    ModelClassName.objects.all()[1]; 不会真的执行查询。也就是说，还是延迟执行。并且会添加条件 limit 1。
    
    ModelClassName.objects.order_by('headline')[0]; === ModelClassName.objects.order_by('headline')[0:1].get();
    不过：当没有匹配的数据数据时，前面的会抛出 IndexError，后者则会抛出 DoesNotExist。
    
    
        【第一条】
        book = Book.objects.first()；
    
        【缓存】
        下面会执行两次 SQL 查询；
        >>> print([e.headline for e in Entry.objects.all()])
        >>> print([e.pub_date for e in Entry.objects.all()])
        
        下面第一次使用时进行查询，第二次再使用就用缓存了；
        >>> queryset = Entry.objects.all()
        >>> print([p.headline for p in queryset]) # Evaluate the query set.
        >>> print([p.pub_date for p in queryset]) # Re-use the cache from the evaluation.
        
        下面会执行两次 SQL 查询；
        >>> queryset = Entry.objects.all()
        >>> print(queryset[5]) # Queries the database
        >>> print(queryset[5]) # Queries the database again
        
        下面只会执行一次 SQL 查询；
        >>> queryset = Entry.objects.all()
        >>> [entry for entry in queryset] # Queries the database
        >>> print(queryset[5]) # Uses cache
        >>> print(queryset[5]) # Uses cache
        
        需要注意的是：简单的print 查询结果集，不会进行缓存。因为 __repr__() 只是返回整个集合的一个切片。
        
        
        【OR】
        默认都是 AND 的关系。
        如果想要使用 OR，需要用到 jdango.db.models.Q 类。主要就是收集参数，进行处理后，交给查询语句。
        q = Q(question__startswith='What');
        Q 对象之间可以使用 & | 来操作，前者代表 AND，后者代表 OR，并返回一个新 Q 对象：
        Q(question__startswith='Who') | Q(question__startswith='What')
        === question LIKE 'Who%' OR question LIKE 'What%'；
        对于 Q 对象，还可以使用 ~ 操作符，表示 NOT：
        Q(question__startswith='Who') | ~Q(pub_date__year=2005)
        
        下面的条件，第二个参数是一个新的 Q 对象（内部使用 OR），然后第一个参数和第二个参数之间使用 AND 来连接：
        Poll.objects.get(
            Q(question__startswith='Who'),
            Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6))
        )
        
        Q 对象和关键字参数可以混合使用，但必须满足一个条件：在参数的顺序上，Q 对象位于关键字参数前面：
        Poll.objects.get(
            Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6)),
            question__startswith='Who',
        )
        如下就是不行了：
        Poll.objects.get(
            question__startswith='Who',
            Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6))
        )
        
        【比较两个模型类实例】
        比如 model1 == model2，本质上就是比较二者的 主键 的值，相当于 model1.id == model2.id。
        当然主键可能不是叫做 id，不过都没有关系，随意。
    
    
    
    
    【新增】
    1、分步；
        ins = ModelClassName(关键字传参：field1=value1,field2=value2,...);
        ins.save();没有返回值，那么怎么判断是否成功呢？估计要 try catch了。
    2、一步到位；
    ModelClassName.objects.create(关键字传参：field1=value1,field2=value2,...);
    
    
    
    【修改】
    1、新增后的修改；
        ins = ModelClassName(关键字传参：field1=value1,field2=value2,...);
        ins.save();没有返回值，那么怎么判断是否成功呢？估计要 try catch了。
        ins.fieldx = valuex;
        ins.save();此时就是修改操作，执行的是 update。估计还是没有返回值。
    
    批量更新
    Entry.objects.filter(pub_date__year=2007).update(headline='Everything is the same');
    这种方式只能用于更新非关联字段，和外键字段。
    对于非关联字段，新值需要给一个常量，比如上面的代码中直接给的字符串常量。
    对于外键，则需要给一个外键模型类的实例：
    >>> b = Blog.objects.get(pk=1)
    >>> Entry.objects.all().update(blog=b) 所有Entry 都属于这个blog。
    返回值是一个数字：表示当前有多少条数据是匹配新值的。这可能导致和实际update的条数不同。
    比如之前已有部分数据该字段的值是新值，那么不会update 它们，但返回值则包含它们。 
    这样的更新方式，也是不会调用每个实例的 save()，所以也不会调用 pre_save()、post_save()。
    如果想要调用这个 save() 链，简单：遍历，执行每个实例的 save()。
    
    update() 还能使用 F() 表达式，比如累加、累减等：
    >>> Entry.objects.all().update(n_pingbacks=F('n_pingbacks') + 1);
    不过和在过滤器 filter()、exclude() 中的用法不同，这里有所限制，只能使用当前模型类的字段，不能使用关联模型类的字段。
    
    
    【删除】
    ins.delete();
    返回值是一个元组：
    (1, {'appName.ModelClassName': 1})
    第一个参数表示总共删除了多少条，第二个表示具体的模型类删除了多少条。
    每个 QuerySet 都有 delete() 方法，删除该集合中的所有数据对应的记录。
    Entry.objects.filter(pub_date__year=2005).delete();返回类似：(5, {'webapp.Entry': 5})。
    对于上面的删除集合，就不会调用每个实例的 delete() 方法了。
    如果我们为模型类定义了delete() 方法（内部当然需要调用父类的delete() 以实现删除功能），想要调用每个实例的 delete() 方法，
    就需要遍历这个集合，然后逐个调用对象的 delete() 方法。
    需要注意的是：QuerySet 的很多方法，都暴露给了 Manager，也就是 ModelClassName.objects 可以直接调用。
    但 delete() 方法没有暴露给它，所以无法 ModelClassName.objects.delete();
    这是基于安全机制，避免使用这个方式删除整个表数据。如果想要删除全表数据：
    ModelClassName.objects.all().delete() 即可。
    
    
    【复制模型类实例】
    blog = Blog(name='My blog', tagline='Blogging is easy')
    blog.save() # blog.pk == 1
    blog.pk = None
    blog.save() # blog.pk == 2
    
    
    【聚合】
    # Total number of books.
    >>> Book.objects.count()
    2452

    # Total number of books with publisher=BaloneyPress
    >>> Book.objects.filter(publisher__name='BaloneyPress').count()
    73

    # Average price across all books.
    >>> from django.db.models import Avg
    >>> Book.objects.all().aggregate(Avg('price')) 简化写法：Book.objects.aggregate(Avg('price'))
    {'price__avg': 34.35}

    # Max price across all books.
    >>> from django.db.models import Max
    >>> Book.objects.all().aggregate(Max('price'))
    {'price__max': Decimal('81.20')}

    # Difference between the highest priced book and the average price of all books.
    >>> from django.db.models import FloatField
    >>> Book.objects.aggregate(
    ...     price_diff=Max('price', output_field=FloatField()) - Avg('price'))
    {'price_diff': 46.85}

    # All the following queries involve traversing the Book<->Publisher
    # foreign key relationship backwards.

    # annotate，相当于给每个实例添加一个属性：属性名要么是关键字参数的关键字，要么是聚合函数的参数 + '_' + 聚合函数名本身全部小写。
    # Each publisher, each with a count of books as a "num_books" attribute.
    >>> from django.db.models import Count
    >>> pubs = Publisher.objects.annotate(num_books=Count('book'))
    >>> pubs
    <QuerySet [<Publisher: BaloneyPress>, <Publisher: SalamiPress>, ...]>
    >>> pubs[0].num_books
    73

    # Each publisher, with a separate count of books with a rating above and below 5
    >>> from django.db.models import Q
    >>> above_5 = Count('book', filter=Q(book__rating__gt=5))
    >>> below_5 = Count('book', filter=Q(book__rating__lte=5))
    >>> pubs = Publisher.objects.annotate(below_5=below_5).annotate(above_5=above_5)
    >>> pubs[0].above_5
    23
    >>> pubs[0].below_5
    12

    # The top 5 publishers, in order by number of books.
    >>> pubs = Publisher.objects.annotate(num_books=Count('book')).order_by('-num_books')[:5]
    >>> pubs[0].num_books
    1323
    
    aggregate() 是 QuerySet 的最终方法，也就是这个方法调用后，不能调用 QuerySet 的其他方法了。
    返回一个字典。
    参数的个数可多个。
    如果每个参数是一个关键字参数那么字典的key 就是关键字本身；
    如果不是关键字参数，那么字典的key就是聚合函数的参数 + '_' + 聚合函数名本身全部小写。
    
    annotate() 不能有多个参数，因为此时会使用 join，而不是子查询，导致结果发生错误。
    对于 Count，可以使用 distinct=True来解决问题：
    >>> q = Book.objects.annotate(Count('authors', distinct=True), Count('store', distinct=True));
    但对于其他的聚合，就没有解决的办法了。
    
    
    【Manager】
    Book.objects.all() 实际执行的是 get_queryset()方法。所以只要重写了这个方法，那么就改变了结果。
    一个模型类可以有多个Manager 类型的实例。
    每个模型类有一个类属性 _default_manager。当有多个Manager 类型的实例时，会把第一个视为该属性的值。
    Django 可能使用这个属性的值处理一些事情，所以尽量保证默认的是第一个：objects = models.Manager()。
    可以通过内部类 Meta 的属性 default_manager_name 来指定。
    
    对于一些第三方的App，可能 objects 这个Manager 被改变了，没有这个属性了。
    此时我们就应该使用 _default_manager 或者 _base_manager 来获取一些数据（后者更多是用于关联的属性）。
    可以通过内部类 Meta 的属性 base_manager_name 来指定 _base_manager 的属性值。
    
    
    
    【执行原始SQL 语句】
    Manager.raw(raw_query--就是查询字符串, params=None, translations=None)
    
    可以使用 as 把表字段名称映射为模型类的属性名，此时就不需要 translations 参数了。
    如果不使用as，或者不全面，可以使用 translations 指定表字段和模型类属性的映射关系，是一个dict：key是表里查出来的字段，value是类属性名。
    
    如果只想要第一条数据，那么不管是index 还是 切片，都不建议使用，而是在 SQL 中添加 limit 语句。
    这样查询出来的，即使一条，结果还是一个集合，此时再使用 index 来获取第一条，就好了。
    '''
        